package tss

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path"
	"sort"
	"time"

	"github.com/julienschmidt/httprouter"
)

// 环境变量配置
const (
	//文件存放目录
	ENV_STORAGE_PATH = "TSS_STORAGE_PATH"
	//文件存放目录
	ENV_HTTP_PORT = "TSS_HTTP_PORT"
	// Basic认证
	ENV_BASIC_AUTH = "TSS_BASIC_AUTH"
)

type Tss struct {
	StoragePath string
	BasicAuth   string
}

func (tss *Tss) Init() {
	dir := os.Getenv(ENV_STORAGE_PATH)
	if dir == "" {
		tss.StoragePath = "./storage"
	} else {
		tss.StoragePath = dir
	}
	tss.BasicAuth = os.Getenv(ENV_BASIC_AUTH)
	//目录若不存在，则创建
	_, err := os.Stat(tss.StoragePath)
	if os.IsNotExist(err) {
		err = os.Mkdir(tss.StoragePath, os.ModePerm)
	}
	httpRouter := httprouter.New()
	httpRouter.GET("/storage/*filepath", tss.basicAuth(tss.fileView))
	httpRouter.PUT("/storage/*filepath", tss.basicAuth(tss.fileUpload))
	httpRouter.GET("/api/storage/*filepath", tss.basicAuth(tss.dirListApi))

	port := os.Getenv(ENV_HTTP_PORT)
	if port == "" {
		port = "8080"
	}
	fmt.Println("Starting server on port", port)
	e := http.ListenAndServe(":"+port, httpRouter)
	if e != nil {
		fmt.Println("Error starting server:", e)
		return
	}
}

func (tss *Tss) basicAuth(next httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		if tss.BasicAuth == "" {
			next(w, r, ps)
			return
		}
		user, pass, ok := r.BasicAuth()
		if !ok || user+":"+pass != tss.BasicAuth {
			w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		next(w, r, ps)
	}
}

type File struct {
	Name    string
	Link    string
	Size    string
	ModTime string
	Dir     bool
}

func (tss *Tss) fileUpload(writer http.ResponseWriter, request *http.Request, ps httprouter.Params) {
	// Retrieve file from request body
	file := request.Body
	defer file.Close()

	// Create the directory if it doesn't exist
	filepath := ps.ByName("filepath")
	dirPath := path.Dir(path.Join(tss.StoragePath, filepath))
	err := os.MkdirAll(dirPath, os.ModePerm)
	if err != nil {
		http.Error(writer, "Unable to create directory", http.StatusInternalServerError)
		return
	}

	// Create a new file in the specified directory
	dst, err := os.Create(path.Join(tss.StoragePath, filepath))
	if err != nil {
		http.Error(writer, "Unable to create the file", http.StatusInternalServerError)
		return
	}
	defer dst.Close()

	// Copy the uploaded file to the created file on the filesystem
	_, err = io.Copy(dst, file)
	if err != nil {
		http.Error(writer, "Unable to save the file", http.StatusInternalServerError)
		return
	}

	// Respond to the client
	writer.WriteHeader(http.StatusCreated)
	writer.Write([]byte("File uploaded successfully"))
}

func (tss *Tss) fileView(writer http.ResponseWriter, request *http.Request, ps httprouter.Params) {
	filePath := ps.ByName("filepath")
	localPath := path.Join(tss.StoragePath, filePath)

	fileInfo, err := os.Stat(localPath)
	if os.IsNotExist(err) {
		http.Error(writer, "File or directory does not exist", http.StatusNotFound)
		return
	}

	if fileInfo.IsDir() {
		tss.fileList(writer, request, ps)
	} else {
		http.ServeFile(writer, request, localPath)
	}
}

func (tss *Tss) fileList(writer http.ResponseWriter, request *http.Request, ps httprouter.Params) {
	data := struct {
		Files []File
	}{
		Files: make([]File, 0),
	}
	filePath := ps.ByName("filepath")
	files, err := os.ReadDir(path.Join(tss.StoragePath, filePath))
	if err != nil {
		http.Error(writer, "Unable to read directory", http.StatusInternalServerError)
		return
	}

	var directories, regularFiles []File

	for _, file := range files {
		name := file.Name()
		// Get file info to retrieve modification time and size
		fileInfo, err := file.Info()
		if err != nil {
			http.Error(writer, "Unable to retrieve file info", http.StatusInternalServerError)
			return
		}
		location, _ := time.LoadLocation("Asia/Shanghai")
		modTime := fileInfo.ModTime().In(location).Format("2006-01-02 15:04:05")
		size := fileInfo.Size()
		fileData := File{
			Name:    name,
			Size:    formatFileSize(size),
			ModTime: modTime,
			Link:    path.Join("/storage", filePath, name),
			Dir:     fileInfo.IsDir(),
		}

		if fileInfo.IsDir() {
			directories = append(directories, fileData)
		} else {
			regularFiles = append(regularFiles, fileData)
		}
	}

	// Sort directories and files by name
	sort.Slice(directories, func(i, j int) bool {
		return directories[i].Name < directories[j].Name
	})
	sort.Slice(regularFiles, func(i, j int) bool {
		return regularFiles[i].Name < regularFiles[j].Name
	})

	// Append directories first, then files
	data.Files = append(data.Files, directories...)
	data.Files = append(data.Files, regularFiles...)

	// If the request is for JSON, return the data as JSON
	if request.Header.Get("Accept") == "application/json" {
		writer.Header().Set("Content-Type", "application/json")
		jsonData, _ := json.Marshal(data.Files)
		writer.Write(jsonData)
		return
	}

	t, err := template.ParseFiles("./ui/file_list.tmpl")
	if err != nil {
		return
	}
	t.Execute(writer, data)
}

func formatFileSize(size int64) string {
	if size < 1024 {
		return fmt.Sprintf("%d Byte", size)
	} else if size < 1024*1024 {
		return fmt.Sprintf("%.2f KB", float64(size)/1024)
	} else if size < 1024*1024*1024 {
		return fmt.Sprintf("%.2f MB", float64(size)/(1024*1024))
	} else {
		return fmt.Sprintf("%.2f GB", float64(size)/(1024*1024*1024))
	}
}
